using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading;
using System.Web.Services.Protocols;
using System.Xml;
using Framework.Interface;

namespace Framework
{
    public static class ExceptionUtil
    {
        #region Format
        public static string Format(Exception ex)
        {
            StringBuilder result;
            using (TextBuilder buffer = TextBuilder.Instance(out result))
            {
                Format(buffer, ex);
            }

            return result.ToString();
        }

        public static void Format(TextBuilder buffer, Exception ex)
        {
            while (ex != null)
            {
                buffer.WriteNameValueLine("Message", ex.Message);
                buffer.WriteNameValueLine("Source", ex.Source);

                if (ex.TargetSite != null)
                {
                    buffer.WriteNameValueLine("TargetSite", ex.TargetSite.ToString());
                }
                
                if (ex.Data != null && ex.Data.Values.Count!= 0)
                {
                    buffer.WriteLine("Exception Info");
                    foreach (DictionaryEntry exceptionData in ex.Data)
                    {
                        BuildExceptionInfoString(buffer, exceptionData);
                    }

                }

                if (ex is System.Web.Services.Protocols.SoapException)
                {
                    System.Web.Services.Protocols.SoapException sex = (System.Web.Services.Protocols.SoapException)ex;

                    buffer.WriteLine(sex.Detail.InnerText);
                }

                buffer.WriteDivider("StackTrace");
                buffer.WriteLine(ex.StackTrace);
                buffer.WriteDivider();

                ex = ex.InnerException;
            }
        }

        public static void BuildExceptionInfoString(TextBuilder buffer, DictionaryEntry exceptionData)
        {
            if (exceptionData.Value == null)
            {
                buffer.WriteNameValueLine(exceptionData.Key.ToString(), "null");
            }
            else if (exceptionData.Value is IToXml)
            {
                buffer.WriteNameValueLine(exceptionData.Key.ToString(), ((IToXml)exceptionData.Value).ToXml());
            }
            else if (exceptionData.Value is object[])
            {
                foreach (object item in (object[])exceptionData.Value)
                {
                    if (item == null)
                    {
                        buffer.WriteNameValueLine(exceptionData.Key.ToString(), "null");
                    }
                    else if (item is IToXml)
                    {
                        buffer.WriteNameValueLine(exceptionData.Key.ToString(), ((IToXml)item).ToXml());
                    }
                    else
                    {
                        buffer.WriteNameValueLine(exceptionData.Key.ToString(), item.ToString());
                    }
                }
            }
            else
            {
                buffer.WriteNameValueLine(exceptionData.Key.ToString(), exceptionData.Value.ToString());
            }
        }
        #endregion

        // *********************************************************************************
        // Helpers
        // *********************************************************************************
        #region ThrowIfNull
        /// <summary>
        /// Throws an exception when objecvt is null
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="message">Exception message</param>
        public static void ThrowIfNull(object obj, string message)
        {
            ThrowIf(obj == null, message);
        }
        /// <summary>
        /// Throws an exception when objecvt is null
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="message">The message.</param>
        /// <param name="formatArgs">The format arguments.</param>
        /// <exception cref="System.Exception"></exception>
        public static void ThrowIfNull(object obj, string message, object[] formatArgs)
        {
            ThrowIf(obj == null, message, formatArgs);
        }
        #endregion

        #region ThrowIf
        /// <summary>
        /// Throws an exception when condition is true
        /// </summary>
        /// <param name="isException">Is this an exception</param>
        /// <param name="message">Exception message</param>
        public static void ThrowIf(bool isException, string message)
        {
            if (isException)
            {
                throw new Exception(message);
            }
        }

        /// <summary>
        /// Throws an exception when condition is true
        /// </summary>
        /// <param name="isException">Is this an exception</param>
        /// <param name="message">Exception message</param>
        /// <param name="formatArgs">Arguments for a formated exception message</param>
        public static void ThrowIf(bool isException, string message, object[] formatArgs)
        {
            if (isException)
            {
                throw new Exception(String.Format(message, formatArgs));
            }
        }
        /// <summary>
        /// Throws an exception when condition is true
        /// </summary>
        /// <param name="isException">Is this an exception</param>
        /// <param name="message">Exception message</param>
        /// <param name="formatArg1">Argument 1 for a formated exception message</param>
        public static void ThrowIf(bool isException, string message, string formatArg1)
        {
            if (isException)
            {
                //Logger.Debug("MSG: " + message);
                //Logger.Debug("formatArg1: " + formatArg1);
                throw new Exception(String.Format(message, formatArg1));
            }
        }

        /// <summary>
        /// Throws an exception when condition is true
        /// </summary>
        /// <param name="isException">Is this an exception</param>
        /// <param name="message">Exception message</param>
        /// <param name="formatArg1">Argument 1 for a formated exception message</param>
        /// <param name="formatArg2">Argument 2 for a formated exception message</param>
        public static void ThrowIf(bool isException, string message, string formatArg1, string formatArg2)
        {
            if (isException)
            {
                throw new Exception(String.Format(message, formatArg1, formatArg2));
            }
        }


        /// <summary>
        /// Throws an exception when condition is true
        /// </summary>
        /// <param name="isException">Is this an exception</param>
        /// <param name="message">Exception message</param>
        /// <param name="formatArg1">Argument 1 for a formated exception message</param>
        /// <param name="formatArg2">Argument 2 for a formated exception message</param>
        /// <param name="formatArg3">Argument 3 for a formated exception message</param>
        public static void ThrowIf(bool isException, string message, string formatArg1, string formatArg2, string formatArg3)
        {
            if (isException)
            {
                throw new Exception(String.Format(message, formatArg1, formatArg2, formatArg3));
            }
        }

        #endregion

        #region TryUntilSuccess
        public static TryUntilSuccessEvent TryUntilSuccess(int noOfTries, int pauseForMilli, TryUntilSuccessDelegate command)
        {
            return TryUntilSuccess(noOfTries, pauseForMilli, command, null);
        }

        public static TryUntilSuccessEvent TryUntilSuccess(int noOfTries, int pauseForMilli, TryUntilSuccessDelegate command, object param)
        {
            var e = new TryUntilSuccessEvent {Param = param};

            int tryCount = 0;

            while (tryCount++ < noOfTries && !e.Success)
            {
                command(e);

                if (!e.Success)
                {
                    //Logger.Debug("Try Again (" + tryCount + " of " + noOfTries + ")");

                    Thread.Sleep(pauseForMilli);
                }
            }

            if (e.Success)
            {
                e.ThrownException = null;
            }

            return e;
        }

        #endregion

        #region TryFileUntilProcessUnlocks

        /// <summary>
        /// Tries the file until process unlocks.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <param name="tryFileUntilProcessUnlockSettings">The settings.</param>
        /// <param name="param">The parameter.</param>
        /// <returns></returns>
        public static TryUntilSuccessEvent TryFileUntilProcessUnlocks(TryUntilSuccessDelegate command, object param = null, TryFileUntilProcessUnlockSettings tryFileUntilProcessUnlockSettings = null)
        {
            var e = new TryUntilSuccessEvent
                {
                    Param = param
                };

            if (tryFileUntilProcessUnlockSettings == null)
            {
                tryFileUntilProcessUnlockSettings = new TryFileUntilProcessUnlockSettings();
            }

            var tryCount = 0;

            while (tryCount++ < tryFileUntilProcessUnlockSettings.NoOfTries)
            {
                command(e);

                if (e.Success)
                {  // Success - StopTrying
                    break;
                }

                //_.P("Try: " + tryCount);
                var ioe = e.ThrownException as IOException;

                if (ioe == null || !ioe.Message.StartsWith("The process cannot access the file"))
                {  // Legitimate Exception - StopTrying
                    break;
                }

                //Logger.Debug("Try Again (" + tryCount + " of " + noOfTries + ")");

                Thread.Sleep(tryFileUntilProcessUnlockSettings.PauseForMilli);
            }

            if (e.Success)
            {
                e.ThrownException = null;
            }

            return e;
        }

        #endregion

        public class TryFileUntilProcessUnlockSettings
        {
            public TryFileUntilProcessUnlockSettings() : this(5, 100)
            {
            }
            public TryFileUntilProcessUnlockSettings(int noOfTries, int pauseForMilli)
            {
                NoOfTries = noOfTries;
                PauseForMilli = pauseForMilli;
            }

            /// <summary>
            /// Gets or sets the no of tries for if the file is available for open.
            /// </summary>
            /// <value>
            /// The no of tries.
            /// </value>
            public int NoOfTries { get; set; }

            /// <summary>
            /// Gets or sets the pause for milliseconds (time between tries).
            /// </summary>
            /// <value>
            /// The pause for milli.
            /// </value>
            public int PauseForMilli { get; set; }
        }
    }
}
